#include <buola/term.h>

#include <buola/gui/cwindow.h>
#include <buola/os/cprocess.h>
#include <buola/gui/cphysicalwindow.h>
#include <buola/widgets/cscrollbar.h>
#include <buola/io.h>
#include <buola/image/cgc.h>
#include <buola/gui/cclipboard.h>
#include <buola/gui/ctoolkit.h>

#include <buola/term/ctermbox.h>
#include <buola/term/cvirtualterm.h>
#include <buola/os/cenv.h>

namespace buola {

using namespace gui;

CTermBox::CTermBox()
{
    mFont=img::CFont::GetStock(L"fixed");
    mBoldFont=img::CFont::GetStock(L"fixedbold");
    mFontSize=mFont->Info().GetSize();
    CalcCellSize();

    SetBackColor(img::CColor(0.177,0.035,0.133));

    mScrollBar=new CScrollBar;
    mScrollBar->SetStep(1,15);
    mScrollBar->SetRange(0,0);
    mScrollBar->SetValue(0,false);
    mScrollBar->eChanged.Connect(&CTermBox::OnScrollBar,this,_1);

    Attach(mScrollBar,gui::ATTACH_EAST);

    mFocused=false;
    mSelect.mSelecting=false;
    mSelect.mOn=false;
}

CTermBox::~CTermBox()
{
}

void CTermBox::CreateVT()
{
    ///\todo think of a nicer way to do this
    msg_info() << Size() << Size().Undefined() << "\n";
    if(Size().Undefined())
        mVT.reset(new CVirtualTerm(25,80));
    else
        mVT.reset(new CVirtualTerm((int)(Size().y/mCellSize.y),(int)(Size().x/mCellSize.x)));
    mVT->sExited.Connect(sExited);
    mVT->sDirty.Connect(&CTermBox::OnVTDirty,this,_1,_2);
    mVT->sDirtyAll.Connect((void(CTermBox::*)())&CTermBox::Refresh,this);
    mVT->sTitleChanged.Connect(sTitleChanged);
    mVT->sSizeChanged.Connect(&CTermBox::OnVTSize,this);

    mFirstLine=0;
}

void CTermBox::OpenApp(const std::string &pCmd)
{
    CreateVT();
    mVT->ForkPty(pCmd);
}

void CTermBox::OpenShell()
{
    OpenApp(CEnv::Get("SHELL","/bin/bash"));
}

static std::wstring sNoTitle;

std::wstring CTermBox::GetTitle()
{
    if(!mVT) return sNoTitle;
    return mVT->GetTitle();
}

bool CTermBox::IsExited()
{
    if(!mVT) return false;
    return mVT->IsExited();
}

void CTermBox::SelectionToClipboard(CClipboard *pCB)
{
    std::wstring lString;

    if(mSelect.mOn)
    {
        for(int r=mSelect.mFrom.r;r<=mSelect.mTo.r;r++)
        {
            CVirtualTerm::PLine lLine=mVT->GetLine(r);
            int lStart=(r==mSelect.mFrom.r)?mSelect.mFrom.c:0;
            int lEnd=(r==mSelect.mTo.r)?mSelect.mTo.c:lLine->mCells.size()-1;

            while(lLine->mCells[lEnd].mC==' '&&lEnd>=lStart) lEnd--;

            for(int i=lStart;i<=lEnd;i++)
                lString+=lLine->mCells[i].mC;
            if(r<mSelect.mTo.r)
                lString+=L'\n';
        }
    }

    pCB->SetString(lString);
}

static img::CColor sColors[]=
{
    img::rgb8(46,52,54),
    img::rgb8(204,0,0),
    img::rgb8(78,154,6),
    img::rgb8(196,160,0),
    img::rgb8(52,101,164),
    img::rgb8(117,80,123),
    img::rgb8(6,152,154),
    img::rgb8(181,185,177),
    img::rgb8(85,87,83),
    img::rgb8(239,41,41),
    img::rgb8(138,226,52),
    img::rgb8(252,233,79),
    img::rgb8(114,159,207),
    img::rgb8(173,127,168),
    img::rgb8(52,226,226),
    img::rgb8(238,238,236)
};

void CTermBox::DrawLine(img::CGC *pGC,double pY,int pLine,int pFrom,int pTo)
{
    using std::swap;
    CVirtualTerm::PLine lLine=mVT->GetLine(pLine);

    int lSelectedFrom,lSelectedTo;

    if(mSelect.mOn&&pLine>=mSelect.mFrom.r&&pLine<=mSelect.mTo.r)
    {
        if(mSelect.mSquare)
            lSelectedFrom=std::min(mSelect.mFrom.c,mSelect.mTo.c);
        else if(pLine==mSelect.mFrom.r)
            lSelectedFrom=mSelect.mFrom.c;
        else
            lSelectedFrom=pFrom;

        if(mSelect.mSquare)
            lSelectedTo=std::max(mSelect.mFrom.c,mSelect.mTo.c);
        else if(pLine==mSelect.mTo.r)
            lSelectedTo=mSelect.mTo.c;
        else
            lSelectedTo=pTo;
    }
    else
    {
        lSelectedFrom=-1;
        lSelectedTo=-1;
    }

    int j=pFrom;
    while(j<pTo)
    {
        double lX=j*mCellSize.x;
        std::wstring lStr;
        lStr+=lLine->mCells[j].mC;
        uint16_t lCurAttr=lLine->mCells[j].mAttr;
        j++;

        while(j<pTo)
        {
            if(j==lSelectedFrom||j==lSelectedTo+1) break;
            if(lLine->mCells[j].mAttr!=lCurAttr) break;
            if(pLine==mVT->GetCursor().r&&mVT->ShouldShowCursor()&&mFocused)
            {
                if(j==mVT->GetCursor().c||j-1==mVT->GetCursor().c)
                {
                    break;
                }
            }

            lStr+=lLine->mCells[j].mC;
            j++;
        }

        int lBack=TERMATTR_GETBACKCOLOR(lCurAttr);
        int lFore=TERMATTR_GETFORECOLOR(lCurAttr);

        if(TERMATTR_ISREVERSE(lCurAttr))
            swap(lBack,lFore);

        if(j-1<=lSelectedTo&&j-1>=lSelectedFrom)
            swap(lBack,lFore);

        if(pLine==mVT->GetCursor().r)
        {
            if(j-1==mVT->GetCursor().c&&mVT->ShouldShowCursor()&&mFocused)
                swap(lBack,lFore);
        }

        if(lBack)
        {
            pGC->SetSource(sColors[lBack]);
            pGC->Path().Rect(CPoint_d(lX,pY),CSize_d(lStr.length()*mCellSize.x,mCellSize.y));
            pGC->FillPath();
        }

        pGC->SetSource(sColors[lFore]);

        if(TERMATTR_ISBOLD(lCurAttr))
        {
            pGC->SetFont(mBoldFont);
        }
        else
        {
            pGC->SetFont(mFont);
        }

        pGC->DrawText(CPoint_d(lX,pY),lStr);
    }

    if(!mFocused&&mVT->ShouldShowCursor()&&pLine==mVT->GetCursor().r)
    {
        int lFore=TERMATTR_GETFORECOLOR(
                            lLine->mCells[mVT->GetCursor().c].mAttr);
        pGC->SetSource(sColors[lFore]);
        pGC->Path().Rect(CPoint_d(mVT->GetCursor().c*mCellSize.x,pY)+0.5,
                            mCellSize-1);
        pGC->SetLineWidth(1.0);
        pGC->StrokePath();
    }
}

void CTermBox::DrawLines(img::CGC *pGC,int pFrom,int pTo)
{
    for(int i=pFrom;i<pTo;++i)
    {
        DrawLine(pGC,(i-mFirstLine)*mCellSize.y,i,0,
                            mVT->GetLine(i)->mCells.size());
    }
}

CCursorPos CTermBox::GetPosition(const CPoint_d &pPoint)
{
    return CCursorPos(mFirstLine+(int)(pPoint.y/mCellSize.y),
                                (int)(pPoint.x/mCellSize.x));
}

void CTermBox::CalcCellSize()
{
    mCellSize.x=mFont->Metrics().mSpaceWidth;
    mCellSize.y=mFont->Metrics().mHeight;
}

void CTermBox::ChangeFontSize(double pChange)
{
    mFontSize*=pChange;
    
    img::CFontInfo lInfo=mFont->Info();
    img::CFontInfo lBoldInfo=mBoldFont->Info();
    lInfo.SetSize(round(mFontSize));
    lBoldInfo.SetSize(round(mFontSize));
    
    mFont=img::CFont::Get(lInfo);
    mBoldFont=img::CFont::Get(lBoldInfo);
    CalcCellSize();

    if(!mVT) return;

    mVT->Resize((int)(Size().y/mCellSize.y),(int)(Size().x/mCellSize.x));
    
    Refresh();
}

void CTermBox::OnExpose(const CExposeEvent &pE)
{
    if(!mVT) return;

    int lFirst=(int)(pE.mRect.t/mCellSize.y)+mFirstLine;
    int lLast=(int)((pE.mRect.b-1)/mCellSize.y)+mFirstLine+1;

    if(lLast<=mFirstLine) return;
    if(lFirst<mFirstLine) lFirst=mFirstLine;
    if(lFirst>=mFirstLine+mVT->GetSize().r) return;
    if(lLast>mFirstLine+mVT->GetSize().r) lLast=mFirstLine+mVT->GetSize().r;

    DrawLines(pE.mGC,lFirst,lLast);
}

void CTermBox::OnChar(const CKeyEvent &pE)
{
    if(!mVT) return;

    std::wstring lStr;
    lStr+=pE.mChar;
    std::string lUStr=utf8(lStr);
    mVT->Write(lUStr.c_str(),lUStr.size());
    
    mScrollBar->SetValue(0);
}

void CTermBox::OnKeyDown(const CKeyEvent &pE)
{
    if(!mVT) return;

    if(pE.mPlainChar) return;
    
    if(pE.mMods&KEYMOD_SHIFT)
    {
        if(pE.mKey==KEY_PGUP)
        {
            mScrollBar->Scroll(-mVT->GetSize().r+1);
            return;
        }
        else if(pE.mKey==KEY_PGDN)
        {
            mScrollBar->Scroll(mVT->GetSize().r-1);
            return;
        }

        if(pE.mMods&KEYMOD_CONTROL)
        {
            if(pE.mKey==KEY_UP)
            {
                int lNewPos=mVT->GetSize().r-mVT->GetTotalRows();
                for(int i=mFirstLine-1;i>lNewPos;i--)
                {
                    if(mVT->GetLine(i)->mCmdStart)
                    {
                        lNewPos=i;
                        break;
                    }
                }
                mScrollBar->SetValue(lNewPos);
                return;
            }
            else if(pE.mKey==KEY_DOWN)
            {
                int lNewPos=0;
                for(int i=mFirstLine+1;i<0;i++)
                {
                    if(mVT->GetLine(i)->mCmdStart)
                    {
                        lNewPos=i;
                        break;
                    }
                }
                mScrollBar->SetValue(lNewPos);
                return;
            }
        }
    }

    if(pE.mChar==L'-'&&pE.mMods&KEYMOD_CONTROL)
    {
        ChangeFontSize(1.0/1.2);
    }
    else if(pE.mChar==L'+'&&pE.mMods&KEYMOD_CONTROL)
    {
        ChangeFontSize(1.2);
    }

    if(mVT->ProcessKeyEvent(pE))
        mScrollBar->SetValue(0);
}

void CTermBox::OnButtonDown(const CMouseEvent &pE)
{
    if(mVT->WantsMouseEvents()&&!(pE.mMods&KEYMOD_SHIFT))
    {
        mVT->ProcessMouseEvent(GetPosition(pE.mPoint),pE);
    }
    else if(pE.mButton==EMouseButton::LEFT)
    {
        if(mSelect.mOn) Refresh();

        mSelect.mSelecting=true;
        mSelect.mOn=false;
        mSelect.mLast=GetPosition(pE.mPoint);
        mSelect.mFrom=mSelect.mLast;
        mSelect.mTo=mSelect.mLast;
        mSelect.mSquare=pE.mMods&KEYMOD_CONTROL;
    }
    else if(pE.mButton==EMouseButton::MIDDLE)
    {
        std::wstring lString;
        if(get_toolkit()->GetClipboard(CClipboard::PRIMARY)->GetString(lString))
        {
            std::string lStr=utf8(lString);
            mVT->Write(lStr.data(),lStr.size());
        }
    }
}

void CTermBox::OnButtonUp(const CMouseEvent &pE)
{
    if(mSelect.mSelecting&&pE.mButton==EMouseButton::LEFT)
    {
        mSelect.mSelecting=false;
        SelectionToClipboard(get_toolkit()->GetClipboard(CClipboard::PRIMARY));
    }
    else if(mVT->WantsMouseEvents())
    {
        mVT->ProcessMouseEvent(GetPosition(pE.mPoint),pE);
    }
}

void CTermBox::OnMouseMove(const CMouseEvent &pE)
{
    using std::swap;
    if(mSelect.mSelecting)
    {
        CCursorPos lNew=GetPosition(pE.mPoint);
        if(lNew!=mSelect.mLast)
        {
            mSelect.mOn=true;
            if(mSelect.mLast==mSelect.mTo)
                mSelect.mTo=lNew;
            else
                mSelect.mFrom=lNew;
            mSelect.mLast=lNew;

            if(mSelect.mTo<mSelect.mFrom)
                swap(mSelect.mTo,mSelect.mFrom);

            mSelect.mSquare=pE.mMods&KEYMOD_CONTROL;

            Refresh();
        }
    }
    else if(mVT->WantsMouseEvents())
    {
        mVT->ProcessMouseEvent(GetPosition(pE.mPoint),pE);
    }
}

void CTermBox::OnMouseWheel(const CMouseEvent &pE)
{
    if(mVT->WantsMouseEvents()&&!(pE.mMods&KEYMOD_SHIFT))
    {
        mVT->ProcessMouseEvent(GetPosition(pE.mPoint),pE);
    }
    else if(pE.mScrollY!=0.0)
    {
        mScrollBar->Scroll(pE.mScrollY);
    }
}

void CTermBox::OnSize(const CGeometryEvent &pE)
{
    if(!mVT) return;

    mVT->Resize((int)(Size().y/mCellSize.y),(int)(Size().x/mCellSize.x));
}

void CTermBox::OnFocusIn(const CFocusEvent &pE)
{
    mFocused=true;
    Refresh();
}

void CTermBox::OnFocusOut(const CFocusEvent &pE)
{
    mFocused=false;
    Refresh();
}

void CTermBox::OnVTSize()
{
    int lMin=mVT->GetSize().r-mVT->GetTotalRows();

    mScrollBar->SetRange(lMin,0);
    mScrollBar->SetValue(0);
    mScrollBar->SetStep(1,mVT->GetSize().r-1);

    if(mFirstLine<lMin)
        mFirstLine=lMin;
}

void CTermBox::OnVTDirty(int pFrom,int pTo)
{
    int lFirstToDraw=std::max(mFirstLine,pFrom);
    int lLastToDraw=std::min(mFirstLine+mVT->GetSize().r-1,pTo);

    if(lFirstToDraw>lLastToDraw) return;
    
    Refresh(CRegion(CRect_d(0,(lFirstToDraw-mFirstLine)*mCellSize.y,Size().x,(lLastToDraw+1-mFirstLine)*mCellSize.y)));
}

void CTermBox::OnScrollBar(int pValue)
{
    mFirstLine=pValue;
    Refresh();
}

/*namespace buola*/ }
